﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

using Weazel.Willows.Model;

namespace Weazel.Willows.PreliminaryModel
{
	public class Module :
		PreliminaryItemBase
	{
		private bool isHardwired;
		public bool IsHardwired
		{
			get { return isHardwired; }
			protected set { isHardwired = value; }
		}

		private Dictionary<string, Register> registers;
		public Dictionary<string, Register> Registers
		{
			get { return registers; }
			set { registers = value; }
		}

		private Dictionary<string, SignalFlowGraph> signalFlowGraphs;
		public Dictionary<string, SignalFlowGraph> SignalFlowGraphs
		{
			get { return signalFlowGraphs; }
			set { signalFlowGraphs = value; }
		}

		private State initialState;

		private Dictionary<string, State> states;
		public Dictionary<string, State> States
		{
			get { return states; }
			set { states = value; }
		}

		private Dictionary<string, Connector> connectors;
		public Dictionary<string, Connector> Connectors
		{
			get { return connectors; }
			set { connectors = value; }
		}

		private List<TransitionSet> transitionSets;


		private List<Channel> channels;

		private List<ChildModule> childModules;

		private IdentifierMap identifierMap;

		public Module(Model model, ref List<ModelError> errors, XmlElement xmlElement)
			: this(model)
		{
			// read from xml
			read(ref errors, xmlElement);
		}

		protected Module(Model model)
			: base(model)
		{
			this.states = new Dictionary<string, State>();
			this.channels = new List<Channel>();
			this.registers = new Dictionary<string, Register>();
			this.signalFlowGraphs = new Dictionary<string, SignalFlowGraph>();
			this.connectors = new Dictionary<string, Connector>();
			this.identifierMap = new IdentifierMap(this);
			this.transitionSets = new List<TransitionSet>();	
			this.childModules = new List<ChildModule>();
		}

		public Weazel.Willows.Model.Module Expand(Model model, IHierarchicalEntity parent, HierarchicalName hierarchicalName, string name)
		{		
			hierarchicalName.Push(name);

			Weazel.Willows.Model.Module module = 
				new Weazel.Willows.Model.Module(parent, name, hierarchicalName.CreateUniqueName());

			module.IsHardwired = IsHardwired;

			// add registers
			foreach (Register register in registers.Values)
				register.ExpandTo(module);

			// add channels
			foreach (Channel channel in channels)
				channel.ExpandTo(module, hierarchicalName);

			// add connectors
			foreach (Connector connector in connectors.Values)
				connector.ExpandTo(module, hierarchicalName);

			// add transitions
			foreach (TransitionSet transitionSet in transitionSets)
				transitionSet.ExpandTo(module);

			// add states
			foreach (State state in states.Values)
				state.ExpandTo(module);

			// add sfgs
			foreach (SignalFlowGraph sfg in signalFlowGraphs.Values)
				sfg.ExpandTo(module);

			// expand children
			foreach (ChildModule childDeclaration in childModules)
			{
				Module moduleTemplate =
					model.TopLevelModules[childDeclaration.ModuleImplementationName];

				Weazel.Willows.Model.Module child =
					moduleTemplate.Expand(
						model, 
						module, 
						hierarchicalName, 
						childDeclaration.ModuleAlias
					);

				module.Children.Add(child.LocalName, child);
			}

			populateReaders(module, hierarchicalName);
			populateWriters(module, hierarchicalName);

			expandChannelsAndConnectors(module);

			hierarchicalName.Pop();
			return module;
		}

		private void expandChannelsAndConnectors(Weazel.Willows.Model.Module module)
		{
			Console.WriteLine(Name);

		  foreach (ChildModule childRef in childModules)
		  {
				Weazel.Willows.Model.Module child = 
					module.Children[childRef.ModuleAlias];

				foreach (IConnector childConnector in child.Connectors.Values)
				{
					if (!childRef.ConnectorToConnectionMap.ContainsKey(childConnector.LocalName))
					{
						string message = 
							string.Format("No channel associated with connectot '{0}' on child dp '{1}'", 
								childConnector.LocalName,
								childConnector.Name
							);

						throw new KeyNotFoundException(message);
					}

					// get the name of whatever is connected to 'connector'
					string connectedTo =
						childRef.ConnectorToConnectionMap[childConnector.LocalName];

					object entity = identifierMap.Lookup(connectedTo);

					//First check if the connector of the child module is 
					//connected to a connector on this (the parent) module.

					if (entity is Connector)
					{
						Connector connector = entity as Connector;

						if (connector.Kind == ConnectorKind.Queue && connector.Direction == ConnectorDirection.In)
						{
							QueueConnectorReaderInterface parentReaderConnector =
								module.Connectors[connectedTo] as QueueConnectorReaderInterface;

							QueueConnectorReaderInterface childReaderConnector =
								childConnector as QueueConnectorReaderInterface;

							RegisterChannel(child, parentReaderConnector, true, childConnector.LocalName);
						}
						else if (connector.Kind == ConnectorKind.Queue && connector.Direction == ConnectorDirection.Out)
						{
							QueueConnectorWriterInterface parentWriterConnector =
								module.Connectors[connectedTo] as QueueConnectorWriterInterface;

							QueueConnectorWriterInterface childWriterConnector =
								childConnector as QueueConnectorWriterInterface;

							RegisterChannel(child, parentWriterConnector, false, childConnector.LocalName);
						}
						else if (connector.Kind == ConnectorKind.Port && connector.Direction == ConnectorDirection.In)
						{
							throw new NotImplementedException();
						}
						else if (connector.Kind == ConnectorKind.Port && connector.Direction == ConnectorDirection.Out)
						{
							throw new NotImplementedException();
						}
						else
							throw new NotSupportedException();
					}
					else if (entity is Channel)
					{
						Channel channel = entity as Channel;

						if (channel.ChannelType == ChannelType.Queue)
						{
							if (childConnector is QueueConnectorReaderInterface)
							{
								QueueConnectorReaderInterface childReaderConnector =
									childConnector as QueueConnectorReaderInterface;

								//channel must be a queue
								Queue queue = module.Channels[connectedTo] as Queue;

								queue.BindReader(childReaderConnector);
								childReaderConnector.AttachedQueue = queue;
							}
							else if (childConnector is QueueConnectorWriterInterface)
							{
								QueueConnectorWriterInterface childWriterConnector =
									childConnector as QueueConnectorWriterInterface;

								Queue queue = module.Channels[connectedTo] as Queue;
								//channel must be a queue

								queue.BindWriter(childWriterConnector);
								childWriterConnector.AttachedQueue = queue;
							}
						}
						else if (channel.ChannelType == ChannelType.Signal)
						{
							if (childConnector is SignalInConnector)
							{
								SignalInConnector childReaderConnector =
									childConnector as SignalInConnector;

								// channel must be signal
								Signal signal = module.Channels[connectedTo] as Signal;

								signal.BindOutput(childReaderConnector);
								childReaderConnector.BindInput(signal);

								//signal.BindReader(childReaderConnector);
								//childReaderConnector.AttachedSignal = signal;
							}
							else if (childConnector is SignalOutConnector)
							{								
								SignalOutConnector childWriterConnector =
									childConnector as SignalOutConnector;
					
								Signal signal = module.Channels[connectedTo] as Signal;

								signal.BindInput(childWriterConnector);
								childWriterConnector.BindOutput(signal);

								//signal.BindWriter(childWriterConnector);
								//childWriterConnector.AttachedSignal = signal;
							}
						}
						else
							throw new NotSupportedException();
					}
				}
		  }
		}

		public void RegisterChannel(
			Weazel.Willows.Model.Module module, 
			IConnector channel, 
			bool readingEnd, 
			string connectorName
		)
		{
			// check if the connector exists
			if (!module.Connectors.ContainsKey(connectorName))
				throw new Exception("Cannot find Connector");

			// get a reference to the connector
			IConnector connector = module.Connectors[connectorName];

			if (channel is Queue && readingEnd)
			{
				// Bind the reading end of queue with local 
				// connector 'connectorName'

				Queue queue = channel as Queue;

				QueueConnectorReaderInterface localReaderInterface =
					connector as QueueConnectorReaderInterface;

				// the local connector must be of the same type
				if (localReaderInterface == null)
					throw new Exception();

				queue.BindReader(localReaderInterface);
				localReaderInterface.AttachedQueue = queue;
			}
			else if (channel is Queue && !readingEnd)
			{
				// Bind the writing end of queue with local connector 
				// namee 'connectorName'

				Queue queue = channel as Queue;

				QueueConnectorWriterInterface localWriterInterface =
					connector as QueueConnectorWriterInterface;

				if (localWriterInterface == null)
					throw new Exception();

				queue.BindWriter(localWriterInterface);
				localWriterInterface.AttachedQueue = queue;
			}
			else if (channel is QueueConnectorReaderInterface)
			{
				// Bind reader connector 'channel' to local connector
				// named 'connectorName'.

				QueueConnectorReaderInterface remoteReaderInterface =
					channel as QueueConnectorReaderInterface;

				// MUST be a reader connector
				QueueConnectorReaderInterface localReaderInterface =
					connector as QueueConnectorReaderInterface;

				// the local connector must be of the same type
				if (localReaderInterface == null)
					throw new Exception(channel.Name);

				remoteReaderInterface.BindReader(localReaderInterface);
				localReaderInterface.AttachedQueue = remoteReaderInterface;
			}
			else if (channel is QueueConnectorWriterInterface)
			{
				// Bind writer connector 'channel' to local connector
				// named 'connectorName'.

				QueueConnectorWriterInterface remoteWriterInterface =
					channel as QueueConnectorWriterInterface;

				// MUST be a writer connector
				QueueConnectorWriterInterface localWriterInterface =
					connector as QueueConnectorWriterInterface;

				if (localWriterInterface == null)
					throw new Exception();

				remoteWriterInterface.BindWriter(localWriterInterface);
				localWriterInterface.AttachedQueue = remoteWriterInterface;
			}
			else if (channel is Signal)
			{
				Signal signal = channel as Signal;
				throw new NotImplementedException();
			}
			else
			{
				throw new NotSupportedException(channel.GetType().ToString());
			}
		}


		private void populateReaders(Weazel.Willows.Model.Module module, HierarchicalName hierarchicalName)
		{
			List<string> readIdentifiers = new List<string>();

			// note casting to base class of SignalFlowGraph. The Always sfg
			// would otherwise cause a cast exception
			foreach (Weazel.Willows.Model.SignalFlowGraph sfg in SignalFlowGraphs.Values)
				readIdentifiers.AddRange(sfg.GetReadIdentifiers());

			foreach (string identifier in readIdentifiers)
			{
				object entity = identifierMap.Lookup(identifier);

				if (entity is Connector)
				{
					Connector connector = entity as Connector;

					if (connector.Kind == ConnectorKind.Queue && connector.Direction == ConnectorDirection.In)
					{											
						Weazel.Willows.Model.QueueReaderInterface reader =
							new QueueReaderInterface(
								module,
								module.Connectors[identifier] as QueueConnectorReaderInterface,
								hierarchicalName.CreateUniqueName(identifier)
							);

						reader.SetReadingDatapath(module);

						// if not already in list of read identifiers ..
						if(!module.Readers.ContainsKey(identifier))
							module.Readers.Add(identifier, reader);
					}
					else if (connector.Kind == ConnectorKind.Queue && connector.Direction == ConnectorDirection.Out)
					{
						// do nothing?
					}
					else if (connector.Kind == ConnectorKind.Port && connector.Direction == ConnectorDirection.In)
					{
						Weazel.Willows.Model.SignalReaderInterface reader =
							new SignalReaderInterface(
								module,
								module.Connectors[identifier] as SignalInConnector,
								hierarchicalName.CreateUniqueName(identifier)
							);

						reader.SetReadingDatapath(module);

						// if not already in list of read identifiers ..
						if (!module.Readers.ContainsKey(identifier))
							module.Readers.Add(identifier, reader);
					}
					else if (connector.Kind == ConnectorKind.Port && connector.Direction == ConnectorDirection.Out)
					{
						throw new NotImplementedException();
					}
					else
						throw new NotSupportedException();
				}
				else if (entity is Channel)
				{
					Channel channel = entity as Channel;

					if (channel.ChannelType == ChannelType.Queue)
					{
						Weazel.Willows.Model.QueueReaderInterface reader =
							new Weazel.Willows.Model.QueueReaderInterface(
								module,
								module.Channels[identifier] as Queue,
								hierarchicalName.CreateUniqueName(identifier)
							);

						reader.SetReadingDatapath(module);
						// if not already in list of read identifiers ..
						if (!module.Readers.ContainsKey(identifier))
							module.Readers.Add(identifier, reader);
					}
					else if (channel.ChannelType == ChannelType.Signal)
					{
						Weazel.Willows.Model.SignalReaderInterface reader =
							new Weazel.Willows.Model.SignalReaderInterface(
								module,
								module.Channels[identifier] as Signal,
								hierarchicalName.CreateUniqueName(identifier)
							);

						reader.SetReadingDatapath(module);
						// if not already in list of read identifiers ..
						if (!module.Readers.ContainsKey(identifier))
							module.Readers.Add(identifier, reader);
					}
					else
						throw new NotSupportedException();
				}
				else if (entity is Register)
				{
					// do nothing
				}
				else
				{
					string message =
						string.Format("Looking up identifier '{0}' returned an unexpected type '{1}'",
							identifier,
							entity.GetType().Name
						);

					throw new Exception(message);
				}
			}
		}

		private void populateWriters(Weazel.Willows.Model.Module module, HierarchicalName hierarchicalName)
		{
			// list of identifiers written by the sfg's in this module
			List<string> writtenIdentifiers = new List<string>();

			// note casting to base class of SignalFlowGraph. The Always sfg
			// would otherwise cause a cast exception
			foreach (Weazel.Willows.Model.SignalFlowGraph sfg in SignalFlowGraphs.Values)
				writtenIdentifiers.AddRange(sfg.GetAssignedIdentifiers());

			foreach (string identifier in writtenIdentifiers)
			{
				object entity = identifierMap.Lookup(identifier);

				if (entity is Connector)
				{
					Connector connector = entity as Connector;

					if (connector.Kind == ConnectorKind.Queue && connector.Direction == ConnectorDirection.In)
					{
						Weazel.Willows.Model.QueueReaderInterface reader =
							new QueueReaderInterface(
								module,
								module.Connectors[identifier] as QueueConnectorReaderInterface,
								hierarchicalName.CreateUniqueName(identifier)
							);

						reader.SetReadingDatapath(module);
						// if not already in list of read identifiers ..
						if (!module.Readers.ContainsKey(identifier))
							module.Readers.Add(identifier, reader);
					}
					else if (connector.Kind == ConnectorKind.Queue && connector.Direction == ConnectorDirection.Out)
					{
						Weazel.Willows.Model.QueueWriterInterface writer =
							new Weazel.Willows.Model.QueueWriterInterface(
								module,
								module.Connectors[identifier] as QueueConnectorWriterInterface,
								hierarchicalName.CreateUniqueName(identifier)
							);

						writer.SetWritingDatapath(module);
						module.Writers.Add(writer);
					}
					else if (connector.Kind == ConnectorKind.Port && connector.Direction == ConnectorDirection.In)
					{
						throw new NotImplementedException();
					}
					else if (connector.Kind == ConnectorKind.Port && connector.Direction == ConnectorDirection.Out)
					{
						Weazel.Willows.Model.SignalWriterInterface writer =
							new SignalWriterInterface(
								module,
								module.Connectors[identifier] as SignalOutConnector,
								hierarchicalName.CreateUniqueName(identifier)
							);

						writer.SetWritingDatapath(module);
						module.Writers.Add(writer);
					}
					else
						throw new NotSupportedException();
					
				}
				else if (entity is Channel)
				{
					Channel channel = entity as Channel;

					if (channel.ChannelType == ChannelType.Queue)
					{
						Weazel.Willows.Model.QueueWriterInterface writer =
							new Weazel.Willows.Model.QueueWriterInterface(
								module,
								module.Channels[identifier] as Queue,
								hierarchicalName.CreateUniqueName(identifier)
							);

						writer.SetWritingDatapath(module);
						module.Writers.Add(writer);
					}
					else if (channel.ChannelType == ChannelType.Signal)
					{		
						Weazel.Willows.Model.SignalWriterInterface writer =
							new SignalWriterInterface(
								module,
								module.Channels[identifier] as Signal,
								hierarchicalName.CreateUniqueName(identifier)
							);

						writer.SetWritingDatapath(module);
						module.Writers.Add(writer);
					}
					else
						throw new NotSupportedException();
				}
				else if (entity is Register)
				{
					// do nothing
				}
				else
				{
					string message =
						string.Format("Looking up identifier '{0}' returned an unexpected type '{1}'",
							identifier,
							entity.GetType().Name
						);

					throw new Exception(message);
				}
			}
		}

		#region Xml
		private void read(ref List<ModelError> errors, XmlElement description)
		{
			System.Diagnostics.Trace.Assert(description.HasAttribute("name"));
			System.Diagnostics.Trace.Assert(description["Datapath"] != null);
			System.Diagnostics.Trace.Assert(description["Controller"] != null);

			// get the name
			this.Name = description.Attributes["name"].Value;

			XmlElement datapath = description["Datapath"];

			if (datapath == null)
			{
				string message =
					string.Format("No datapath found for module '{0}'", Name);

				errors.Add(new ModelError(message));
				return;
			}

			ReadDatapath(ref errors, datapath);

			XmlElement controller = description["Controller"];

			ReadController(ref errors, controller);
		}

		public void ReadController(ref List<ModelError> errors, XmlElement controller)
		{
			readStates(ref errors, controller);
			readTransitionSets(ref errors, controller);
		}

		public void ReadDatapath(ref List<ModelError> errors, XmlElement datapath)
		{
			readConnectors(ref errors, datapath);
			readChildModules(ref errors, datapath);
			readQueues(ref errors, datapath);
			readSignals(ref errors, datapath);
			readRegisters(ref errors, datapath);
			readSignalFlowGraphs(ref errors, datapath);
		}

		private void readChildModules(ref List<ModelError> errors, XmlElement element)
		{
			foreach(XmlElement childXmlElement in element.GetElementsByTagName("Use"))
			{
				ChildModule child = new ChildModule(childXmlElement);
				childModules.Add(child);				
			}
		}

		private void readRegisters(ref List<ModelError> errors, XmlElement description)
		{
			foreach (XmlElement element in description.GetElementsByTagName("Register"))
			{
				Register register = new Register(Model, element, ref identifierMap);

				// check for duplicates
				if (Registers.ContainsKey(register.Name))
				{
					string message =
						String.Format(
							"Duplicate definition of register '{0}' in datapath '{1}'",
							register.Name,
							Name
						);

					errors.Add(new ModelError(message));
				}
				else
				{
					Registers.Add(register.Name, register);
				}
			}
		}

		private void readConnectors(ref List<ModelError> errors, XmlElement element)
		{
			foreach (XmlElement description in element.GetElementsByTagName("Connector"))
			{
				Connector connector = new Connector(Model, identifierMap, errors, description);
				connectors.Add(connector.Name, connector);
			}
		}

		private void readSignals(ref List<ModelError> errors, XmlElement description)
		{
			foreach (XmlElement element in description.GetElementsByTagName("Signal"))
			{
				Channel channel = new Channel(Model, identifierMap, errors, element);
				channels.Add(channel);
			}
		}

		private void readQueues(ref List<ModelError> errors, XmlElement description)
		{
			foreach (XmlElement element in description.GetElementsByTagName("Queue"))
			{
				Channel channel = new Channel(Model, identifierMap, errors, element);
				channels.Add(channel);
			}
		}

		private void readSignalFlowGraphs(ref List<ModelError> errors, XmlElement description)
		{
			foreach (XmlElement element in description.GetElementsByTagName("SignalFlowGraph"))
			{
				SignalFlowGraph signalFlowGraph = new SignalFlowGraph(Model, this, element);

				// check for duplicates	
				if (SignalFlowGraphs.ContainsKey(signalFlowGraph.LocalName))
				{
					string message =
						string.Format("Duplicate definition of SFG '{0}' in module '{1}'",
						signalFlowGraph.Name,
						Name
					);

					errors.Add(new ModelError(message));
				}
				else
				{
					SignalFlowGraphs.Add(signalFlowGraph.LocalName, signalFlowGraph);
				}
			}
		}

		private void readStates(ref List<ModelError> errors, XmlElement description)
		{
			foreach (XmlElement element in description.GetElementsByTagName("State"))
			{
				State state = new State(Model, this, element);

				if (States.ContainsKey(state.Name))
				{
					string message =
						String.Format(
							"Duplicate definition of state '{0}' in module '{1}'",
							state.Name,
							Name
						);

					errors.Add(new ModelError(message));
				}

				if (state.IsInitial)
					this.initialState = state;
				
				this.states.Add(state.Name, state);
			}
		}

		private void readTransitionSets(ref List<ModelError> errors, XmlElement description)
		{
			foreach (XmlElement element in description.GetElementsByTagName("TransitionSet"))
			{
				TransitionSet transitionSet = new TransitionSet(element);	

				transitionSets.Add(transitionSet);				
			}
		}
		#endregion
	}
}
